#ifndef SOCKET_HPP
#define SOCKET_HPP

#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <memory>

namespace NetWork
{
    const static int default_backlog = 5;
    const static int default_sockfd = -1;

    enum Error
    {
        SocketError = 1,
        BindError,
        ListenError,
        AcceptError,
        SendError,
        CloseError,
    };

    // template method pattern -> pure virtual base class which provides the interface
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void CreateSocketOrDie() = 0;
        virtual void BindSocketOrDie(uint16_t port) = 0;
        virtual void ListenSocketOrDie(int backlog) = 0;
        virtual std::shared_ptr<Socket> AcceptSocketOrDie(std::string* peerip, uint16_t* peerport) = 0;
        virtual bool ConnectSocketOrDie(const std::string& peerip, uint16_t peerport) = 0;
        virtual int GetSockfd() const = 0;
        virtual void SetSockfd(int fd) = 0;
        virtual void CloseSocket() = 0;
        virtual bool Recv(std::string* data, int len) = 0;
        virtual void Send(const std::string& data) = 0;
        virtual void ReuseAddr() = 0;
    public:
        void BuildListenSocketMethod(uint16_t port, int backlog = default_backlog)
        {
            CreateSocketOrDie();
            ReuseAddr();
            BindSocketOrDie(port);
            ListenSocketOrDie(backlog);
        }
        bool BuildConnectSocketMethod(const std::string& peerip, uint16_t peerport)
        {
            CreateSocketOrDie();
            return ConnectSocketOrDie(peerip, peerport);
        }
        void BuildNomalSocketMethod(int sockfd)
        {
            SetSockfd(sockfd);
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd = default_sockfd) : _sockfd(sockfd) {}
        ~TcpSocket() {}

        void CreateSocketOrDie() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if(_sockfd == -1) { exit(SocketError); }
        }
        void BindSocketOrDie(uint16_t port) override
        {
            struct sockaddr_in addr;
            addr.sin_addr.s_addr = INADDR_ANY;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            if(bind(_sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { exit(BindError); }
        }
        void ListenSocketOrDie(int backlog) override
        {
            if(listen(_sockfd, backlog) == -1) { exit(ListenError); }
        }

        std::shared_ptr<Socket> AcceptSocketOrDie(std::string* peerip, uint16_t* peerport) override
        {
            struct sockaddr_in addr;
            socklen_t len = sizeof(addr);
            int new_sockfd = accept(_sockfd, (struct sockaddr*)&addr, &len);
            if(new_sockfd == -1) { exit(AcceptError); }
            *peerip = inet_ntoa(addr.sin_addr);
            *peerport = ntohs(addr.sin_port);
            return std::make_shared<TcpSocket>(new_sockfd);
        }
        bool ConnectSocketOrDie(const std::string& peerip, uint16_t peerport) override
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_addr.s_addr = inet_addr(peerip.c_str());
            addr.sin_port = htons(peerport);
            if(connect(_sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { return false; }
            return true;
        }

        int GetSockfd() const override
        {
            return _sockfd;
        }
        void SetSockfd(int fd) override
        {
            _sockfd = fd;
        }
        void CloseSocket() override
        {
            if(close(_sockfd) == -1) { exit(CloseError); }
        }

        bool Recv(std::string* data, int len) override
        {
            char* buf = new char[len];
            ssize_t recv_len = recv(_sockfd, buf, len, 0);
            if(recv_len > 0)
            {
                buf[recv_len] = '\0';
                *data += buf;
                delete[] buf;
                return true;
            }
            // recv_len == 0 or -1
            delete[] buf;
            return false;
        }
        void Send(const std::string& data) override
        {
            ssize_t send_len = send(_sockfd, data.c_str(), data.size(), 0);
            if(send_len == -1) { exit(SendError); }
        }
        void ReuseAddr() override
        {
            // resolve binding failures due to server restart
            int optval = 1;
            setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &optval, sizeof(optval));
        }

    private:
        int _sockfd;
    };
}







#endif // SOCKET_HPP